package com.yanghui.dtracing.logback;

import com.alibaba.fastjson.JSON;
import com.yanghui.dtracing.core.opentracing.DTracerSpan;
import com.yanghui.dtracing.core.opentracing.DTracerTags;
import com.yanghui.dtracing.core.opentracing.reporter.Reporter;
import com.yanghui.dtracing.core.utils.NetUtils;
import io.opentracing.tag.Tags;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

public class LogbackReporter implements Reporter {

    private final Logger log = LoggerFactory.getLogger(LogbackReporter.class);

    private InetAddress inetAddress;

    @Override
    public void init() {
        inetAddress = NetUtils.getLocalAddress();
    }

    @Override
    public void report(DTracerSpan span) {
        try{
            String spanId = span.context().toSpanId();
            String parentId = span.context().getParentId();
            MDC.put("spanId",id(spanId));
            if("-1".equals(parentId)){
                MDC.put("parentId","0");
            }else {
                MDC.put("parentId",id(parentId));
            }
            MDC.put("msName",span.getServiceName());
            MDC.put("traceId",span.context().toTraceId());
            String component = span.getStrTags().get(Tags.COMPONENT.getKey());
            MDC.put("component",component);
            if("Http".equalsIgnoreCase(component)){
                MDC.put("className",span.getStrTags().get("http.path"));
                MDC.put("method","");
            }else {
                MDC.put("className",span.getClassName());
                MDC.put("method",span.getOperationName());
            }
            MDC.put("costTime",String.valueOf(span.getCostTime()));
            MDC.put("startTime", DateUtil.toDateStr(span.getStartTime()));
            MDC.put("endTime", DateUtil.toDateStr(span.getEndTime()));
            MDC.put("asyn",span.context().getBaggageItems().get("asyn"));
            MDC.put("autoId",span.context().getBaggageItems().get("autoId"));
            if(inetAddress != null){
                MDC.put("instance",inetAddress.getHostAddress());
            }
            if(null != spanId){
                String[] ss = spanId.split("\\.");
                MDC.put("level",String.valueOf(ss.length - 1));
            }
            Boolean error = span.getBolTags().get(Tags.ERROR.getKey());
            if(null != error){
                MDC.put("success",error ? "N" : "Y");
            }else {
                MDC.put("success","Y");
            }
            String type = span.getStrTags().get(DTracerTags.TYPE.getKey());
            MDC.put("type",type);
            Map<String,String> traceTags = new HashMap<>();
            if("Feign".equalsIgnoreCase(component) || "Zuul".equalsIgnoreCase(component)){
                traceTags.put("target_service",span.getStrTags().get("peer.service"));
                traceTags.put("url",span.getStrTags().get("peer.url"));
                traceTags.put("target_host",span.getStrTags().get("peer.hostname"));
                MDC.put("targetService",span.getStrTags().get("peer.service"));
                MDC.put("targetInstance",span.getStrTags().get("peer.hostname"));
            }else if("mysql".equalsIgnoreCase(component)){
                traceTags.put("host",span.getStrTags().get("db.instance"));
                traceTags.put("sql",span.getStrTags().get("db.statement"));
                MDC.put("targetService","mysql");
                MDC.put("targetInstance",span.getStrTags().get("db.instance"));
            }else if("jedis".equalsIgnoreCase(component)){
                traceTags.put("host",span.getStrTags().get("db.instance"));
                MDC.put("targetService","jedis");
                MDC.put("targetInstance",span.getStrTags().get("db.instance"));
            }else if("Redisson".equalsIgnoreCase(component)){
                traceTags.put("host",span.getStrTags().get("db.instance"));
                traceTags.put("redisCommand",span.getStrTags().get("db.statement"));
                MDC.put("targetService","redisson");
                MDC.put("targetInstance",span.getStrTags().get("db.instance"));
            }
            MDC.put("traceTags", JSON.toJSONString(traceTags));
            log.info("trace-log");
            if("exit".equalsIgnoreCase(type)){
                log.info("trace-topology");
            }
        }finally {
            MDC.clear();
        }
    }

    private static String id(String spanId){
        return toLowerHex(spanIdToLong(spanId));
    }

    private static long spanIdToLong(String spanId) {
        return FNV64HashCode(spanId);
    }

    private static long FNV64HashCode(String data) {
        long hash = 0xcbf29ce484222325L;
        for (int i = 0; i < data.length(); ++i) {
            char c = data.charAt(i);
            hash ^= c;
            hash *= 0x100000001b3L;
        }
        return hash;
    }

    private static String toLowerHex(long v) {
        char[] data = new char[16];
        writeHexLong(data, 0, v);
        return new String(data);
    }

    private static void writeHexLong(char[] data, int pos, long v) {
        writeHexByte(data, pos + 0, (byte) ((v >>> 56L) & 0xff));
        writeHexByte(data, pos + 2, (byte) ((v >>> 48L) & 0xff));
        writeHexByte(data, pos + 4, (byte) ((v >>> 40L) & 0xff));
        writeHexByte(data, pos + 6, (byte) ((v >>> 32L) & 0xff));
        writeHexByte(data, pos + 8, (byte) ((v >>> 24L) & 0xff));
        writeHexByte(data, pos + 10, (byte) ((v >>> 16L) & 0xff));
        writeHexByte(data, pos + 12, (byte) ((v >>> 8L) & 0xff));
        writeHexByte(data, pos + 14, (byte) (v & 0xff));
    }

    static final char[] HEX_DIGITS =
            {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    static void writeHexByte(char[] data, int pos, byte b) {
        data[pos + 0] = HEX_DIGITS[(b >> 4) & 0xf];
        data[pos + 1] = HEX_DIGITS[b & 0xf];
    }

    @Override
    public void close() {
    }
}
